#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <wchar.h>

int main()
{
    char *currLocale = setlocale(LC_ALL, NULL); // 第二个参数为NULL，会将当前地域设置的名称返回
    printf("%s\n", currLocale);
    printf("%d\t%d\n", MB_LEN_MAX, MB_CUR_MAX);

    // 为了保证程序执行时，字符能够正确解读，最好将程序环境切换到本地化环境。
    setlocale(LC_ALL, "zh_CN.UTF-8");
    currLocale = setlocale(LC_ALL, NULL);
    printf("%s\n", currLocale);

    printf("%d\t%d\n", MB_LEN_MAX, MB_CUR_MAX);
    printf("ABC\n");          // ABC
    printf("\101\102\103\n"); // ABC
    printf("\x41\x42\x43\n"); // ABC

    printf("\u2022 Bullet 1\n");     // • Bullet 1
    printf("\U00002022 Bullet 1\n"); // • Bullet 1

    // C 语言预设只有基本字符，才能使用字面量表示，其它字符都应该使用码点表示，并且当前系统还必须支持该码点的编码方法。
    // 所谓基本字符，指的是所有可打印的 ASCII 字符，但是有三个字符除外：@、$、`。
    // 因此，遇到非英语字符，应该将其写成 Unicode 码点形式。
    char *s = "\u6625\u5929";
    printf("%zd\t%lu\t%s\n", sizeof(s), strlen(s), s); // 8       6       春天

    // 如果当前系统是 UTF-8 编码，可以直接用字面量表示多字节字符。
    char *s2 = "春天";
    printf("%zd\t%lu\t%s\n", sizeof(s2), strlen(s2), s2); // 8       6       春天

    // 注意，\u + 码点和\U + 码点的写法，不能用来表示 ASCII 码字符（码点小于0xA0的字符），只有三个字符除外：0x24（$），0x40（@）和0x60（`）。

    char *s3 = "\u0024\u0040\u0060";
    printf("%s\n", s3); // @$`

    // C 语言允许使用u8前缀，对多字节字符串指定编码方式为 UTF-8。
    char *s4 = u8"春天";
    printf("%zd\t%lu\t%s\n", sizeof(s4), strlen(s4), s4); // 8       6       春天

    // char s5[2] = {'春', '天'}; // 编译错误：character too large for enclosing character literal type
    // printf("%zd\t%lu\t%s\n", sizeof(s5), strlen(s5), s5);

    // 上一面的多字节字符串，每个字符的字节宽度是可变的。这种编码方式虽然使用起来方便，但是很不利于字符串处理，因此必须逐一检查每个字符占用的字节数。所以除了这种方式，C 语言还提供了确定宽度的多字节字符存储方式，称为宽字符（wide character）。
    // 所谓“宽字符”，就是每个字符占用的字节数是固定的，要么是2个字节，要么是4个字节。这样的话，就很容易快速处理。
    // 宽字符有一个单独的数据类型 wchar_t，每个宽字符都是这个类型。它属于整数类型的别名，可能是有符号的，也可能是无符号的，由当前实现决定。该类型的长度为16位（2个字节）或32位（4个字节），足以容纳当前系统的所有字符。它定义在头文件wchar.h里面
    wchar_t wc = L'春';
    printf("%lc\n", wc);

    wchar_t *s5 = L"春天"; // 宽字符串的结尾也有一个空字符，不过是宽空字符，占用多个字节。
    printf("%ls\n", s5);   // 春天

    wchar_t s5_arr[] = {L'春', L'天', L'\0'};
    printf("%ls\n", s5_arr); // 春天

    // 多字节字符处理函数
    // 1) mblen()函数返回一个多字节字符占用的字节数。它的原型定义在头文件stdlib.h。
    // int mblen(const char* mbstr, size_t n);
    // 它接受两个参数，第一个参数是多字节字符串指针，一般会检查该字符串的第一个字符；第二个参数是需要检查的字节数，这个数字不能大于当前系统单个字符占用的最大字节，一般使用MB_CUR_MAX。
    // 它的返回值是该字符占用的字节数。如果当前字符是空的宽字符，则返回0；如果当前字符不是有效的多字节字符，则返回-1。

    char *mbs1 = "春天";
    printf("%d\n", mblen(mbs1, MB_CUR_MAX)); // 3

    char *mbs2 = "abc";
    printf("%d\n", mblen(mbs2, MB_CUR_MAX)); // 1

    // 2) wctomb()函数（wide character to multibyte）用于将宽字符转为多字节字符。它的原型定义在头文件stdlib.h。
    // int wctomb(char* s, wchar_t wc);
    // wctomb()接受两个参数，第一个参数是作为目标的多字节字符数组，第二个参数是需要转换的一个宽字符。它的返回值是多字节字符存储占用的字节数量，如果无法转换，则返回-1。
    wc = L'牛';
    char mbStr[10] = "";

    int nBytes = 0;
    nBytes = wctomb(mbStr, wc);

    printf("%s\n", mbStr);  // 牛
    printf("%d\n", nBytes); // 3

    // 3) mbtowc()用于将多字节字符转为宽字符。它的原型定义在头文件stdlib.h。
    // int mbtowc(wchar_t* wchar,const char* mbchar,size_t count);
    // 它接受3个参数，第一个参数是作为目标的宽字符指针，第二个参数是待转换的多字节字符指针，第三个参数是多字节字符的字节数。
    // 它的返回值是多字节字符的字节数，如果转换失败，则返回-1。

    char *mbchar = "逼";
    wchar_t *pwc = &wc;

    nBytes = mbtowc(pwc, mbchar, 3);

    printf("%d\n", nBytes); // 3
    printf("%lc\n", *pwc);  // 逼
    // 上面示例中，mbtowc()将多字节字符“逼”转为宽字符wc，返回值是mbchar占用的字节数（占用3个字节）。

    // 4) wcstombs()用来将宽字符串转换为多字节字符串。它的原型定义在头文件stdlib.h。
    // size_t wcstombs(char* mbstr,const wchar_t* wcstr,size_t count);
    // 它接受三个参数，第一个参数mbstr是目标的多字节字符串指针，第二个参数wcstr是待转换的宽字符串指针，第三个参数count是用来存储多字节字符串的最大字节数。
    // 如果转换成功，它的返回值是成功转换后的多字节字符串的字节数，不包括尾部的字符串终止符；如果转换失败，则返回-1。
    // 如果wcstombs()的第一个参数是 NULL，则返回转换成功所需要的目标字符串的字节数。

    char mbs[20];
    wchar_t *wcs = L"春天";

    nBytes = 0;
    nBytes = wcstombs(mbs, wcs, 20);

    printf("%s\n", mbs);    // 春天
    printf("%d\n", nBytes); // 6

    // 5) mbstowcs()用来将多字节字符串转换为宽字符串。它的原型定义在头文件stdlib.h。
    // size_t mbstowcs(wchar_t* wcstr,const char* mbstr,size_t count);
    // 它接受三个参数，第一个参数wcstr是目标宽字符串，第二个参数mbstr是待转换的多字节字符串，第三个参数是待转换的多字节字符串的最大字符数。
    // 转换成功时，它的返回值是成功转换的多字节字符的数量；转换失败时，返回-1。如果返回值与第三个参数相同，那么转换后的宽字符串不是以 NULL 结尾的。

    char *mbs_2 = "天气不错";
    wchar_t wcs_2[20];

    nBytes = 0;
    nBytes = mbstowcs(wcs_2, mbs_2, 20);

    printf("%ls\n", wcs_2); // 天气不错
    printf("%d\n", nBytes); // 4
    // 上面示例中，多字节字符串mbs被mbstowcs()转为宽字符串，成功转换了4个字符，所以该函数的返回值为4。
    // 如果mbstowcs()的第一个参数为NULL，则返回目标宽字符串会包含的字符数量。
    return 0;
}